<?php

namespace App\Models\Trip\RefundNeedPay;

use App\Models\Common\Channel\Channel;
use App\Models\Train\TrainOrderPassengers\Repositories\Interfaces\TrainOrderPassengersRepositoryInterface;
use App\Models\Train\TrainOrderPassengers\TrainOrderPassenger;
use App\Models\Train\TrainOrders\Repositories\Interfaces\TrainOrdersRepositoryInterface;
use App\Models\Train\TrainOrders\TrainOrder;
use App\Models\Trip\OrderPay\OrderPay;
use App\Models\Trip\OrderPay\Repositories\Interfaces\OrderPayRepositoryInterface;
use App\Models\Trip\TripOrderRefund\TripOrderRefund;
use App\Trip\Enums\OrderBusiness;
use App\Trip\Enums\TripTypes;
use App\Trip\Orders\ApplyRefund\TrainOrderApplyRefund;
use App\Trip\Train\Order\Impl\PiaoChiOrderPassengerRefundImpl;
use App\Trip\Train\Order\Interfaces\OrderPassengerRefundInterface;
use App\Trip\Traits\Pay\SubPayModelAttributeTrait;
use App\Trip\Wechat\Pay\PayRes\PayResult;
use App\Trip\Wechat\Pay\WechatPay;
use Eloquent;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Throwable;

/**
 * App\Models\Trip\RefundNeedPay\RefundNeedPay
 *
 * @property int         $id
 * @property string      $user_id            用户
 * @property string      $pay_sn             支付单号
 * @property string      $order_sn           订单单号
 * @property string      $trade_sn           支付交易单号
 * @property string      $passenger_id       乘客id
 * @property int         $sequence           乘客序号
 * @property int         $need_refund        需要退款 1 不需要 0
 * @property string      $pay_type           支付类型 1 微信支付
 * @property string      $pay_title          支付标题
 * @property string      $pay_amount         支付金额
 * @property string      $receipts           实收金额
 * @property int         $pay_state          支付状态 0待支付 1已支付 3已取消 2 失败
 * @property string      $trade_desc         交易描述
 * @property string      $cancel_reason      取消原因
 * @property string      $failed_reason      失败原因
 * @property string      $need_refund_reason 需要退款原因：如支付后无法退款 则需要全额退款
 * @property string      $mchid              支付商戶id
 * @property string      $appid              appid
 * @property string|null $expired_at         交易结束时间
 * @property Carbon|null $created_at
 * @property Carbon|null $updated_at
 * @method static Builder|RefundNeedPay newModelQuery()
 * @method static Builder|RefundNeedPay newQuery()
 * @method static Builder|RefundNeedPay query()
 * @method static Builder|RefundNeedPay whereAppid($value)
 * @method static Builder|RefundNeedPay whereCancelReason($value)
 * @method static Builder|RefundNeedPay whereCreatedAt($value)
 * @method static Builder|RefundNeedPay whereExpiredAt($value)
 * @method static Builder|RefundNeedPay whereFailedReason($value)
 * @method static Builder|RefundNeedPay whereId($value)
 * @method static Builder|RefundNeedPay whereMchid($value)
 * @method static Builder|RefundNeedPay whereNeedRefund($value)
 * @method static Builder|RefundNeedPay whereNeedRefundReason($value)
 * @method static Builder|RefundNeedPay whereOrderSn($value)
 * @method static Builder|RefundNeedPay wherePassengerId($value)
 * @method static Builder|RefundNeedPay wherePayAmount($value)
 * @method static Builder|RefundNeedPay wherePaySn($value)
 * @method static Builder|RefundNeedPay wherePayState($value)
 * @method static Builder|RefundNeedPay wherePayTitle($value)
 * @method static Builder|RefundNeedPay wherePayType($value)
 * @method static Builder|RefundNeedPay whereReceipts($value)
 * @method static Builder|RefundNeedPay whereSequence($value)
 * @method static Builder|RefundNeedPay whereTradeDesc($value)
 * @method static Builder|RefundNeedPay whereTradeSn($value)
 * @method static Builder|RefundNeedPay whereUpdatedAt($value)
 * @method static Builder|RefundNeedPay whereUserId($value)
 * @mixin Eloquent
 */
class RefundNeedPay extends Model
{
    use HasFactory, SubPayModelAttributeTrait;

    protected $table = 'refund_need_pay';

    protected $fillable
        = [
            "user_id",
            "pay_sn",
            "order_sn",
            "passenger_id",
            "sequence",
            "pay_type",
            "pay_title",
            "pay_amount",
            "receipts",
            "pay_state",
            "need_refund",
            "need_refund_reason",
            "trade_desc",
            "cancel_reason",
            "failed_reason",
            "expired_at",
            "mchid",
            "appid",
        ];

    public function getReceipts(): float
    {
        return $this->getAttribute('receipts');
    }

    /**
     * @throws Exception
     */
    public function addByTrainOrderPassenger(OrderPassengerRefundInterface $order_passenger_refund, WechatPay $pay, Channel $channel): self
    {
        $should_pay_amount = $order_passenger_refund->getShouldPayAmount();
        if ($should_pay_amount == 0) {
            throw new Exception("订单订单，不需要支付，无法获取支付信息");
        }
        $passenger  = $order_passenger_refund->getPassenger();
        $order_info = $passenger->getOrderInfo();
        $canRefund  = new TrainOrderApplyRefund($order_info, $passenger);
        if (!$canRefund->setIgnoreWorkTimeError()->canApplyRefund()) {
            throw new Exception($canRefund->getErrorMsg());
        }

        if ($record = $this->findOneByTrainOrderPassenger($passenger)) {
            // 记录存在 如果金额不一样 则更新金额
            if ($record->getPayAmount() != $should_pay_amount) {
                $record->update([
                    'pay_amount' => $should_pay_amount,
                ]);
            }
            return $record;
        } else {
            $addData = [
                "user_id"      => $passenger->getUserId(),
                "pay_sn"       => generate_order_id(TripTypes::TRAIN, OrderBusiness::PAY),
                "order_sn"     => $passenger->getOrderSn(),
                "passenger_id" => $passenger->getPassengerId(),
                "sequence"     => $passenger->getSequence(),
                "pay_type"     => OrderPay::PAY_TYPE_WECHAT,
                "pay_title"    => "车票退票补交费用",
                "pay_amount"   => $should_pay_amount,
                "pay_state"    => OrderPay::PAY_STATE_WAITING,
                "mchid"        => $pay->getMchid(),
                "appid"        => $channel->getAppId(),
            ];
            DB::transaction(function () use (&$record, $addData, $passenger, $pay, $channel, $should_pay_amount) {
                // 添加记录
                $record = $this->create($addData);
                // 添加 orderPay 记录 避免业务单号冲突 ,便于后期退款
                $orderPayRepo = app(OrderPayRepositoryInterface::class);
                $orderPayRepo->createTrainOrderRefundNeedPay($record);
            });

        }

        return $record;
    }

    public function getPassenger(): ?TrainOrderPassenger
    {
        return $this->getTrainOrderInfo()?->getPassengerByPassengerIdAndSequence($this->getPassengerId(), $this->getPassengerSequence());
    }

    public function getTrainOrderInfo(): ?TrainOrder
    {
        /**
         * @var TrainOrdersRepositoryInterface $orderRepo
         */
        $orderRepo = app(TrainOrdersRepositoryInterface::class);
        return $orderRepo->getOrderInfo($this->getOrderSn());
    }

    public function getOrderSn(): string
    {
        return $this->getAttribute('order_sn');
    }

    public function getPassengerId(): string
    {
        return $this->getAttribute('passenger_id');
    }

    public function getPassengerSequence(): int
    {
        return $this->getAttribute('sequence');
    }

    public function findOneByTrainOrderPassenger(TrainOrderPassenger $passenger): ?self
    {
        $where = [
            "user_id"      => $passenger->getUserId(),
            "order_sn"     => $passenger->getOrderSn(),
            "passenger_id" => $passenger->getPassengerId(),
            "sequence"     => $passenger->getSequence(),
        ];
        return $this->where($where)->first();
    }

    /**
     * @throws Exception|Throwable
     */
    public function updatePayByPayResult(string $pay_sn, PayResult $result)
    {
        $pay_info = $this->findOneByPaySn($pay_sn);
        if (!$pay_info) {
            throw new Exception("refund_need_pay :updatePayByPayResult,支付回调-支付单数据不存在：{$pay_sn}");
        }
        // 如果支付成功
        if ($pay_info->isPaySuccess()) {
            return true;
        }
        if ($result->isSuccess()) {
            DB::transaction(function () use ($result, $pay_info) {
                // 更新支付状态 1
                $this->updateOne([
                    'pay_state'  => OrderPay::PAY_STATE_SUCCESS,
                    'trade_sn'   => $result->getTransactionId(),
                    'receipts'   => $result->getPayAmount(),
                    'trade_desc' => $result->getTradeStateDesc(),
                ],
                    $pay_info);
                // 出发退款
                /**
                 * @var TripOrderRefund                         $refund_info
                 * @var TrainOrderPassengersRepositoryInterface $orderPassengerRepo
                 */
                $orderPassengerRepo     = app(TrainOrderPassengersRepositoryInterface::class);
                $order_info             = $pay_info->getTrainOrderInfo();
                $passenger              = $pay_info->getPassenger();
                $order_passenger_refund = new PiaoChiOrderPassengerRefundImpl($order_info, $passenger);
                $order_passenger_refund->setIgnoreWorkTimeError();
                $orderPassengerRepo->refund($order_passenger_refund);
            });

        } else {
            $this->updateOne([
                'pay_state'     => OrderPay::PAY_STATE_FAILED,
                'failed_reason' => $result->getTradeStateDesc(),
            ],
                $pay_info);
        }
        $pay_info->refresh();
        // 同步更新
        $orderPayRepo = app(OrderPayRepositoryInterface::class);
        $orderPayRepo->createTrainOrderRefundNeedPay($pay_info);
    }

    public function findOneByPaySn(string $pay_sn): ?self
    {
        return $this->where('pay_sn', $pay_sn)->first();
    }

    public function updateOne(array $data, $model = null): bool
    {
        if ($model instanceof self) {
            return $model->update($data);
        }
        return $this->update($data);
    }
}
